iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0
Software Development

系統設計就像九頭蛇:打造社群網站的 30 天系列 第 30 篇

Day 30 從一台 Server 到今天的系統設計回顧

  • 分享至 

  • xImage
  •  

30 天前,這個系列是從一句話開始的:

System Design 的第一步,不是畫架構圖,而是先把問題問清楚。

今天是最後一天,與其再加一個新元件,不如看看我們怎麼從 Day 1 那句話,走到今天這個樣子的。

起點

Day 1 完全沒有畫任何架構圖,只做了一件事:把「設計一個像 Threads 的社群平台」這種模糊需求,拆成 Functional / Non-Functional Requirements,再用 Back-of-the-envelope calculation 把使用者數量換算成 QPS、儲存空間、頻寬。

Day 2 才真正動手,而且刻意做得很陽春:

瀏覽器 -> Server -> Database

一台 Server、一個 Database、Session 直接放在 Memory 裡。

Day 3 補上 Feed 該怎麼組:找出 Follow 對象、撈出 Post、排序、用 Cursor 分頁。

這三天定下了整個系列的敘事節奏:

先讓系統能動,再觀察它在哪裡開始喘不過氣,而不是一開始就把所有元件都塞進架構圖。

Phase 1:把單台 Server 的極限逐一打開(Day 4-9)

Day 4 先處理前後端怎麼溝通(REST vs GraphQL),確認現階段 REST 已經夠用。接著開始正面對決「一台 Server 不夠了」的問題:

  • Day 5:Vertical Scaling 撞到硬體天花板,於是走向 Horizontal Scaling,引入 Load Balancer
  • Day 6:Server 一多,Session 立刻出問題,於是搬進 Distributed Cache,變成 Stateless Architecture
  • Day 7:Server 問題解決了換 Database 靠夭,用 Primary / Read Replica 分攤 Read Traffic
  • Day 8:即使有 Replica,重複的 Query 還是浪費,加入 Cache 減少對 DB 的依賴
  • Day 9:圖片、影片這種大檔案不該擠進 DB 或 Cache,交給 CDN + Object Storage

這六天的共通劇本幾乎一樣:解決一個瓶頸,下一層馬上冒出新的瓶頸,而每個新元件在解決問題的同時,也帶來自己專屬的新麻煩(Replication Lag、Cache Invalidation、CDN 的 TTL 與 Purge)。

Phase 2:把「等待」從主流程拆出去(Day 10-12)

  • Day 10:補上使用者上傳檔案到 CDN 之間的處理管線(Upload → Processing → CDN)
  • Day 11:做出最陽春的 Notification,同步寫入,簡單但一多就會拖慢主流程
  • Day 12:用 Message Queue 把 Notification 拆成非同步,Producer/Consumer 解耦合,也順便打開了 Event-driven Architecture 的大門

這個 Phase 的核心教訓:不是所有工作都需要使用者等在原地看它做完。

Phase 3:從一包程式碼走向服務化(Day 13-16)

系統元件夠多之後,Application 本身也開始扛不住:

  • Day 13:討論 Monolith 該不該拆成 Microservices,拆完之後用 gRPC 處理內部溝通
  • Day 14:Service 一多,Client 記不住那麼多位址,於是有了 API Gateway
  • Day 15:討論 Gateway 的 Rate Limiting(Token Bucket 等三種策略)
  • Day 16:回頭檢視「是不是什麼資料都該放 SQL」,得出「按存取模式挑合適工具,而不是整套搬家」的結論

這個 Phase 處理的不是效能瓶頸,而是組織與架構層面的擴展,一旦團隊變大、Service 變多了,就需要新的協作與治理方式。

Phase 4:資料分散後的一致性問題(Day 17-21)

Day 16 留下一個伏筆:資料分散在 SQL、NoSQL、Cache 之後,「怎麼確保各處看到的資料一致」變得更複雜。Day 17 把這個問題拉高到理論層次,完整拆解 CAP Theorem,並用 Primary Failover 當作具體案例。

CAP 不只是理論,它立刻在接下來四天派上用場:

  • Day 18:Read Replica 還是不夠,用 Sharding 把資料真正切開分散
  • Day 19:Follow 這種「關聯表」本身變成 Scaling 難題,Shard Key 不管選哪個方向都會讓另一個方向的查詢變成昂貴的 Scatter-Gather,最後靠 SQL + Redis + Graph Database 的 Polyglot Persistence 分工解決
  • Day 20:Feed 的 Fan-out 策略從最陽春的 Fan-out on Read,演化成能同時應付一般帳號與熱門帳號的 Hybrid Fan-out
  • Day 21:把「熱門帳號/熱門貼文」這種集中式流量問題獨立出來討論,系統整體能力和能否處理單一熱點的極端流量,是兩個不同的問題

這五天是整個系列理論密度最高的段落:先有理論框架(CAP),才看得懂後面每一個實際決策在取捨什麼。

Phase 5:不是所有問題都該塞給同一個 Database(Day 22-24)

  • Day 22:搜尋不能靠 SQL 的 LIKE,需要 Inverted Index 這種完全不同的資料結構
  • Day 23:Like/View/Follower Count 這種簡單的 +1,在高流量下藏著 Write Hotspot,靠批次彙總、分片計數、Redis 累加解決
  • Day 24:Notification 從「Worker 寫完 DB 就結束」推進到用 SSE + Pub/Sub 做到近乎即時推播,而且完全相容 Day 6 的 Stateless 架構

這三天的主題其實是同一句話的不同展開:該用什麼工具解決問題,取決於問題本身的形狀,而不是「反正都塞進主 Database」。

Phase 6:把視角拉到單一元件之外(Day 25-27)

  • Day 25:用 Logging / Metrics / Tracing 幫整個系統裝上一雙眼睛,讓分散在各個元件裡的問題無所遁形
  • Day 26:回到前端,把 Day 3 的 Cursor Pagination API 串成使用者真正順手的 Infinite Scroll + Virtualization + Prefetch 體驗
  • Day 27:討論 Region 化 Read Replica 到 Active-active 多主架構的取捨,而這也是 CAP Theorem 在跨區域網路下被放大的版本

這三天不再是「解決一個新問題」,而是把視角從單一元件拉高:先看懂整個系統現在健不健康(Observability),再看向使用者真正操作的介面(Frontend),最後看向系統散佈在世界各地的樣子(Multi-region)。

Phase 7:系統資安(Day 28-29)

  • Day 28:換上攻擊者的視角,重新盤點 PokeThreads 的 Attack Surface
  • Day 29:補上 WAF、完整的 Authorization(不只是「你是誰」,還有「你能做什麼」)、以及 Abuse Prevention

完整系統架構圖

把前面七個 Phase 提到的元件全部攤開,拼成 PokeThreads 今天的完整樣貌:

Browser / Mobile App
        │
        ▼
       CDN                                                    (Day 9)
        │
        ▼
  Load Balancer                                                (Day 5)
        │
        ▼
   API Gateway                                                  (Day 14)
   ├─ Authentication / Authorization (Day 6, 29)
   ├─ Rate Limiting (Day 15)
   └─ WAF (Day 29)
        │
        ▼  gRPC 內部溝通 (Day 13)
        │
        ├──→ User Service (Day 2, 6, 29)
        │       └─→ Distributed Cache(Session,Day 6)+ Sharded SQL Primary/Replica(Day 7, 18)
        │
        ├──→ Post Service (Day 2, 9, 10)
        │       └─→ Sharded SQL(Day 7, 18)+ Upload Pipeline → Object Storage(Day 9-10)
        │
        ├──→ Feed Service (Day 3, 20, 21)
        │       └─→ Distributed Cache(Fan-out Cache,Day 8, 20)+ Sharded SQL(Day 7, 18)
        │
        ├──→ Follow / Social Graph Service (Day 19)
        │       └─→ SQL + Redis + Graph Database(Polyglot Persistence,Day 19)
        │
        ├──→ Notification Service (Day 11, 12, 24)
        │       └─→ Message Queue / Pub-Sub(Day 12, 24)→ SSE 即時推播(Day 24)
        │
        ├──→ Search Service (Day 22)
        │       └─→ Search Engine / Inverted Index(Day 22)
        │
        └──→ Counter Service (Day 21, 23)
                └─→ Distributed Cache(分片計數器,Day 21, 23)

橫跨所有元件:Observability — Logging / Metrics / Tracing(Day 25)
橫跨所有節點:Multi-region 部署 — Region 化 Replica → Active-active(Day 27)

收束四條伏筆線

這個系列刻意埋了幾條貫穿全程的線,走到這裡可以收尾了:

  • Auth:Day 2 的 Stateful Session in Memory → Day 6 因為 Server 變多被迫搬進 Distributed Cache / JWT → Day 29 補上 Authorization,完成「你是誰」到「你能做什麼」的完整拼圖
  • 前後端溝通:Day 4 決定用 REST 把後端搭起來 → Day 26 站在前端角度,把這支 API 串成真正順手的使用者體驗
  • 一致性:Day 17 建立 CAP Theorem 的理論框架 → Day 18-20 在 Sharding、Social Graph、Fan-out 裡一次次實際套用這套取捨
  • Rate Limiter:Day 15 從效能與資源保護的角度做出 Token Bucket 等技術細節 → Day 28 重新檢視它作為資安防禦工具的角色與極限

系統問題與解法

把這 30 天出現過的解法抽象化來看,會發現很多次其實是同一種問題換了個場景重新出現。整理成一張對照表:

遇到的問題 常見解法 對應章節
單一節點會是 Single Point of Failure 增加 Redundancy(多一份備援 + Failover) Day 5、7、17
某個操作會拖住主流程,讓使用者乾等 拆成非同步,丟進 Message Queue 背景處理 Day 11-12
同樣的資料一直被重複查詢 加一層 Cache Day 8
單一機器裝不下所有資料、扛不住寫入量 Sharding,把資料真正切開分散 Day 18
查詢方式跟資料形狀不合(搜尋、關係圖) 換成對應的專用資料庫,而不是硬塞進同一個 DB Day 16、19、22
單一 Server 撐不住流量 Horizontal Scaling + Load Balancer Day 5
Service 之間耦合太緊,牽一髮動全身 拆成獨立服務,用明確的介面溝通 Day 13-14
流量或 API 被濫用 Rate Limiting Day 15
多節點資料不同步 依資料的重要程度選一致性等級,而不是要求全部強一致 Day 17
少數 Key/帳號特別熱門,局部過載 針對熱點特殊處理(分拆計數器、額外快取),而不是整體擴容 Day 21、23
系統出狀況卻不知道從何查起 Logging + Metrics + Tracing Day 25

這個也不是一本「查表就能解題」的手冊,同一個問題在不同規模、不同限制條件下,答案可能完全不一樣(這也是這系列從頭到尾的主題)。

它更像是一份問題的分類法,先認出眼前麻煩屬於哪一類,才知道該往哪個方向想解法,而不用每次都從零開始摸索。

用一句話總結這 30 天

Day 1 就講過、之後都在印證的同一句話:

System Design 沒有唯一正確答案,只有在目前限制條件下最合理的取捨。

Vertical Scaling 不是錯的,只是撐不了太久;Stateful Server 不是笨方法,只是換到多台 Server 就不管用;SQL 不是過時的技術,但不是每一種資料都適合塞進同一張表。

這系列裡新增的每一個元件,都不是因為「大公司都這樣做」,而是因為前一天的架構,剛好在某個具體場景下撐不住了。

沒有終點:還有一長串問題這系列沒處理

到這裡很容易有個錯覺,以為系統設計出一套「夠完整」的架構之後,接下來就是照著擴充就好。實際上相反,每次解決一個瓶頸,比較像是把某個沒處理的問題往後延,而不是把它徹底消滅。

我們的 PokeThreads 走到今天,還有一長串問題完全沒被碰過,列出來給大家自己接著往下研究:

資料一致性

  • Day 12 用 Message Queue 拆出非同步流程,但如果 Consumer 處理到一半 Crash,Notification 少發或發兩次,要怎麼確保 Exactly-once?
  • 使用者刪除一篇貼文之後,Fan-out Cache、Search Index、CDN 上的舊圖片,各自要多久才會同步跟上?「使用者以為已經刪除了,但某個地方還看得到」這段空窗期,要怎麼縮短?
  • 一個橫跨多個 Microservice 的操作(例如刪除帳號要同時清掉 Post、Follow、Notification、Search Index),沒有 Database Transaction 保護,中途失敗該怎麼辦?這正是 Saga Pattern 想解決的問題,這系列完全沒提到

維運與韌性

  • Day 25 做出了 Observability,但看得到問題不等於扛得住問題。如果某個 Service 掛了,其他 Service 要怎麼優雅降級,而不是被拖著一起垮(Circuit Breaker、Bulkhead 這類韌性模式沒有討論過)
  • 新版本要怎麼上線才不會中斷服務(Blue-Green、Canary Release)?上線後發現問題,要怎麼快速 Rollback?
  • Database Schema 要改欄位時,Production 環境要怎麼做到 Zero-downtime Migration?
  • Disaster Recovery:如果整個機房燒了,多久備份一次、RTO/RPO 訂在多少?

功能面

  • 私訊(Direct Message)完全沒出現在這 30 天裡,那其實是另一組完全不同的 Read/Write Pattern 與一致性要求
  • Day 24 用 SSE 做即時通知,但那服務的是「App/瀏覽器開在前景」的使用者,App 在背景或被關閉時要怎麼推播,APNs、FCM 這類 Push Notification 服務完全沒提到
  • 內容審核(Content Moderation):Day 29 提到用 AI 偵測 Bot,但沒討論真人發的違規、騷擾內容要怎麼被偵測、申訴、下架
  • 多語系、時區這種國際化(i18n)問題,一旦 PokeThreads 真的要做全球化,會冒出一堆眉角
  • 離線分析(Data Warehouse、Batch ETL):Day 25 的 Metrics 是給工程團隊看系統健不健康,但「哪些內容比較多人喜歡」這種給產品、營運看的離線分析管線,是完全不同的系統

為什麼刻意不寫這些

不是這些不重要,而是每一個都大到可以自己獨立寫一個系列,像是 Saga Pattern、Circuit Breaker、Push Notification Infra,隨便一個都能再多寫好幾篇,更不用說我自己查資料看資料也頭昏眼花了。

這系列從頭到尾想討論的是「看到瓶頸、判斷取捨」的思考方式,而不是窮舉一份「大型系統該有哪些元件」的清單,這種清單就算真的寫得出來,過幾年也會因為新技術出現而過時。

留白比塞好塞滿更實在:一個系統設計系列如果宣稱自己已經涵蓋所有面向,多半代表寫的人低估了真實系統的複雜度。

System Design 沒有一個「做完了」的終點,只有「現在夠用了,之後再說」。

延伸閱讀

PokeThreads 是虛構的練習案例,如果想看業界怎麼討論「設計一個類似 Twitter/Threads 的社群平台」這種經典題目,這兩篇很適合接著讀:

這兩篇跟這系列的側重點不太一樣:它們比較像是「一次性設計出完整方案」的教學,這系列則是刻意把時間拉長成 30 天,強調「每個元件是在哪個瓶頸下才被加進來的」。兩種角度合著看,會更清楚 System Design 沒有單一標準流程,只是每個人切入的順序不同。

寫在最後

從 Day 2 那個「瀏覽器 -> Server -> Database」的陽春架構,到今天涵蓋 Load Balancer、Stateless Server、Sharded Database、Cache、CDN、Message Queue、Microservices、Search、Counter System、多區域部署、資安防護的系統(即使上一段列的那一長串問題都還沒填),中間沒有任何一步是憑空跳過去的,每一個已經做出來的元件,都能回答「為什麼是現在、為什麼是這個」。

這大概就是 System Design 這件事最值得練習的地方:它不是背下一份「大型系統該有哪些元件」的清單,而是練習在每一個當下,看清楚現在真正的瓶頸是什麼,再決定該加什麼、該用什麼取捨去解決它。

非常歡迎留下你的感想,也歡迎到我的個人網站看其他內容。

感謝一路和我研究如何壯大一個社群網站,我的系列文章也剛好在連假的最後一天完結,雖然可能晚了,但仍祝福你有個愉快的假期(下一週還有雙十連假呢)。


上一篇
Day 29 資安不能只靠一道牆:Security Architecture
系列文
系統設計就像九頭蛇:打造社群網站的 30 天 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言